The current best practice for using git to manage collaborative software projects is known as trunk-based development. Under this model, small changes are frequently made in different branches, then merged into the main “trunk” (i.e. the main or master branch) of the repo after passing peer review. The steps look like this:
An issue is opened
a developer or user notices a bug, requests a feature, or asks a question.
Engage in the issue comments
to clarify the issue, ask for a reproducible example, etc.
Work on the issue
create a new branch and switch to it.
write tests that will pass when the issue is resolved.
write or edit code to resolve the issue.
(possibly) write more tests to make sure edge cases and failure modes are handled.
write/update documentation if needed.
make sure your tests pass and the package still builds.
Create a pull request
assign or request a reviewer.
the reviewer reviews your code.
you make any requested changes.
the reviewer approves your pull request once they’re happy with it.
merge the pull request.
Celebrate that you resolved an issue!
You can have multiple issues open at any stage of the process at a time. You might start working on a feature, switch to fixing a time-sensitive bug and resolve it, then later go back to working on that feature. Meanwhile, collaborators are working on other issues too! This process enables highly collaborative and asynchronous work.
Continuous integration + git = magic
It would be a bummer if you or a collaborator forgot a crucial step of the process, like running the unit tests or linting your code, and accidentally merged buggy/broken/bad code into the main branch of your project. The good news is: You don’t have to remember everything! Let the machines do it for you automatically!
Continuous integration is a practice where tests and other code quality checks are automatically run before code changes are merged into the main branch.
How does this modify our git workflow? When we open a pull request or push a commit to main, the CI service will run a workflow we define to run our checks, so we don’t have to do it manually!
CI service options
GitHub Actions
Travis
Jenkins
CircleCI
Azure DevOps
We’ll use GitHub Actions because it’s easy to setup when you already have your repo hosted on GitHub, and they provide a lot of computing resources for free.
Building a CI workflow with GitHub Actions
We’re going to create a CI workflow that runs on all pushes and pull requests to the default branch (typically “main” or “master”). Workflows are defined with YAML files to specify how to configure the machine that runs the workflow, install dependencies, and run commands.
Let’s start by creating a small workflow that prints “Hello, world!” and lists the files in the package.
Every Actions workflow resides in .github/workflows/ and needs:
on – events that trigger the workflow
jobs – list of independent jobs each with steps to run in sequence.
.github/workflows/greet.yml
# name of the workflowname: greet # when the workflow should runon:push:branches:- main- masterpull_request:branches:- main- master# independent jobs in the workflowjobs: # this workflow just has one job called "greet"greet: # the operating system to use for this workflowruns-on: ubuntu-latest # list of steps in the workflowsteps: # use an action provided by github to checkout the repo-uses: actions/checkout@v3 # a custom step that runs a couple shell commands-name: List run: | echo "listing files in the bioinitio directory" ls bionitio # a custom step that runs R code-name: Greet run: print("Hello, world!") # Replace `shell: Rscript {0}` with `shell: python {0}` to run Python code instead!shell: Rscript {0}
“Hello world” action
Add this file to your project repo, replace bionitio with the name of your package, then commit and push it to GitHub.
On GitHub, go to the Actions tab of your repo. Is the workflow running?
Once the workflow finishes, it will either have a green checkmark (✅ success) or a red x (❌ failure).
Click on the workflow run. Then under ‘jobs’, click on the job ‘greet’. You’re now viewing the log file for the job. You can click on the arrows to expand the details for each step.
greet status
In Slack, react with ✅ or ❌ to indicate the status of your workflow.
Test suite
This initial “hello world” workflow is cute, but not very useful. Let’s edit the workflow to run our test suite for us automatically!
The r-lib actions assume that the top level of your repo is the same as the top level of your R package. If that’s not the case, you’ll need to specify the working-directory.
For my example project, bionitio-r is the top level of the git repo, and from there the R package resides in bionitio:
name: cion:push:branches:- main- masterpull_request:branches:- main- masterjobs:build:runs-on: ubuntu-lateststeps:-uses: actions/checkout@v3-name: Set up Python 3.11uses: actions/setup-python@v3with:python-version:"3.11"-name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt fi-name: Test with pytest run: | pytest .
In each of these workflows, the action checks out the repo, installs R or Python, installs the dependencies of the package, then runs the tests. If any of your tests fail, the whole actions workflow will fail too.
Testing with CI
Modify your CI workflow to run the test suite and push it. Does the CI workflow succeed or fail?
You may get failures if you haven’t been running your unit tests as you develop your code base. Take a few minutes to open issues for each test that failed.
Workflow status badges
Each Actions workflow has a status badge that indicates whether the action is passing or failing. You may have come across status badges in GitHub README files of packages you use. Putting a CI status badge in the README file is a popular way for project maintainers to prominently display that CI is set up and it’s working!
Add the workflow status badge to your README
Under the Actions tab, click the name of the workflow (e.g. ci), click the triple dots menu (...) in the upper right corner, and select Create status badge.
In the pop-up menu, click Copy status badge Markdown, paste it into your README.md file, then commit and push your change.
React to the slack message with ✅ when you’re finished.
Code coverage
codecov is free for open source projects!
codecov status badge
Lint and style code
R: lintr & styler
Python: flake8 & black
Document
R: roxygen2
Python: sphinx
Setup a documentation website
GitHub Pages will host your docs for free!
Other topics & resources to explore
Other ways to trigger workflows
on release
manual dispatch
cron schedule
branch protection
prevent bad PRs from getting merged
pre-commit hooks
run checks, style code, etc before you even commit